package com.dmagic.cms.common.util;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.common.collect.Maps;

/**
 * Http and Servlet utility class.
 * 
 * @author songlin.li
 */
public class ServletUtils {
    protected transient static final Log logger = LogFactory.getLog(ClassUtil.class);

    // -- Content Type Defined --//
    public static final String TEXT_TYPE = "text/plain";

    public static final String JSON_TYPE = "application/json";

    public static final String XML_TYPE = "text/xml";

    public static final String HTML_TYPE = "text/html";

    public static final String JS_TYPE = "text/javascript";

    public static final String EXCEL_TYPE = "application/vnd.ms-excel";

    // -- Header Defined --//
    public static final String AUTHENTICATION_HEADER = "Authorization";

    // -- common value defined --//
    public static final long ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;

    /**
     * @description : Set the client cache expired time Header.
     * @param response
     * @param expiresSeconds
     */
    public static void setExpiresHeader(HttpServletResponse response, long expiresSeconds) {
        // Http 1.0 header
        response.setDateHeader("Expires", System.currentTimeMillis() + expiresSeconds * 1000);
        // Http 1.1 header
        response.setHeader("Cache-Control", "private, max-age=" + expiresSeconds);
    }

    /**
     * @description : Set banned the client cache of Header.
     * @param response
     */
    public static void setDisableCacheHeader(HttpServletResponse response) {
        // Http 1.0 header
        response.setDateHeader("Expires", 1L);
        response.addHeader("Pragma", "no-cache");
        // Http 1.1 header
        response.setHeader("Cache-Control", "no-cache, no-store, max-age=0");
    }

    /**
     * @description : Set LastModified Header.
     * @param response
     * @param lastModifiedDate
     */
    public static void setLastModifiedHeader(HttpServletResponse response, long lastModifiedDate) {
        response.setDateHeader("Last-Modified", lastModifiedDate);
    }

    /**
     * @description : Set Etag Header.
     * @param response
     * @param etag
     */
    public static void setEtag(HttpServletResponse response, String etag) {
        response.setHeader("ETag", etag);
    }

    /**
     * @description : According to the browser If-Modified-Since Header, Calculation if the
     * file has been modified. If no changes, checkIfModify return false ,set
     * 304 not modify status.
     * 
     * @param lastModified The content of the last modification time.
     */
    public static boolean checkIfModifiedSince(HttpServletRequest request, HttpServletResponse response,
            long lastModified) {
        long ifModifiedSince = request.getDateHeader("If-Modified-Since");
        if ((ifModifiedSince != -1) && (lastModified < ifModifiedSince + 1000)) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return false;
        }
        return true;
    }

    /**
     * @description : According to the browser If-None-Match Header, Calculation Etag whether
     * invalid. if Etag is valid, checkIfNoneMatch return false, set 304 not
     * modify status.
     * 
     * @param etag Content ETag.
     */
    public static boolean checkIfNoneMatchEtag(HttpServletRequest request, HttpServletResponse response, String etag) {
        String headerValue = request.getHeader("If-None-Match");
        if (headerValue != null) {
            boolean conditionSatisfied = false;
            if (!"*".equals(headerValue)) {
                StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");

                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
                    String currentToken = commaTokenizer.nextToken();
                    if (currentToken.trim().equals(etag)) {
                        conditionSatisfied = true;
                    }
                }
            } else {
                conditionSatisfied = true;
            }

            if (conditionSatisfied) {
                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                response.setHeader("ETag", etag);
                return false;
            }
        }
        return true;
    }

    /**
     * @description :Set the browser pop-up download dialog Header.
     * 
     * @param fileName After download the file name.
     */
    public static void setFileDownloadHeader(HttpServletRequest request, HttpServletResponse response, String fileName) {
        try {
            // Chinese filenames support
            if (request.getHeader("User-Agent").toUpperCase().indexOf("FIREFOX") > 0) {
                // firefox browser
                fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
            } else if (request.getHeader("User-Agent").toUpperCase().indexOf("MSIE") > 0) {
                // IE browser
                fileName = URLEncoder.encode(fileName, "UTF-8");
            }
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        } catch (UnsupportedEncodingException e) {
        }
    }

    /**
     * @description :With the same prefix made the Request Parameters. The results of
     * Parameter name has been to remove the prefix.
     */
    public static Map<String, Object> getParametersStartingWith(ServletRequest request, String prefix) {
        Assert.notNull(request, "Request must not be null");
        Enumeration paramNames = request.getParameterNames();
        Map<String, Object> params = new TreeMap<String, Object>();
        if (prefix == null) {
            prefix = "";
        }
        while (paramNames != null && paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            if ("".equals(prefix) || paramName.startsWith(prefix)) {
                String unprefixed = paramName.substring(prefix.length());
                String[] values = request.getParameterValues(paramName);
                if (values == null || values.length == 0) {
                    // Do nothing, no values found at all.
                } else if (values.length > 1) {
                    params.put(unprefixed, values);
                } else {
                    params.put(unprefixed, values[0]);
                }
            }
        }
        return params;
    }

    /**
     * <strong>Request To Entity</strong>
     * <p>
     * Request to Entity
     * </p>
     * 
     * @param entity bean
     * @param request
     * @return object
     * @author songlin.li
     */
    public <T> T parameterToEntity(HttpServletRequest request, Class<T> entity, String prefix) {
        Assert.notNull(entity, "Entity must be not null");
        Object bean = null;
        try {
            bean = entity.newInstance();
        } catch (InstantiationException e1) {
            logger.error(e1.getMessage(), e1);
        } catch (IllegalAccessException e1) {
            logger.error(e1.getMessage(), e1);
        }
        if (StringUtil.isBlank(prefix)) {
            String entityName = entity.getName().toLowerCase();
            String[] entityNames = entityName.split("\\.");
            entityName = entityNames[entityNames.length - 1];
            prefix = entityName + ".";
        } else if (!prefix.endsWith(".")) {
            prefix += ".";
        }
        Map<String, String[]> m = getParametersToMapArrayStartingWith(request, prefix);
        bean = getFiledNames(bean, entity, m);

        return (T) bean;
    }

    private <T> Object getFiledNames(Object bean, Class<T> entity, Map<String, String[]> m) {
        Field[] fields = entity.getDeclaredFields();
        int len = fields.length;
        for (int i = 0; i < len; i++) {
            Field field = entity.getDeclaredFields()[i];
            String fieldName = field.getName();
            if (m.get(fieldName) != null) {
                try {
                    setFieldValue(field, bean, m.get(fieldName));
                } catch (IllegalAccessException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        entity = (Class<T>) entity.getSuperclass();
        if (entity != null) {
            bean = getFiledNames(bean, entity, m);
            try {
                Field needSaveFields = entity.getDeclaredField("needSaveFields");
                needSaveFields.set(bean, m.keySet());
            } catch (SecurityException e) {

            } catch (NoSuchFieldException e) {

            } catch (IllegalArgumentException e) {

            } catch (IllegalAccessException e) {

            }
        }
        return bean;
    }

    /**
     * @description : The data object assignment to designated the corresponding attributes
     * 
     * @param field
     * @param bean
     * @param value
     * @throws IllegalAccessException
     */
    private void setFieldValue(Field field, Object bean, String[] values) throws IllegalAccessException {
        // Take the field of data types
        String fieldType = field.getType().getName();
        field.setAccessible(true);
        String value = values[0];
        if (StringUtil.isNotBlank(value)) {
            try {
                if (fieldType.equals("java.lang.String")) {
                    field.set(bean, value);
                } else if (fieldType.equals("java.lang.Integer") || fieldType.equals("int")) {
                    field.set(bean, Integer.valueOf(value));
                } else if (fieldType.equals("java.lang.Long") || fieldType.equals("long")) {
                    field.set(bean, Long.valueOf(value));
                } else if (fieldType.equals("java.lang.Float") || fieldType.equals("float")) {
                    field.set(bean, Float.valueOf(value));
                } else if (fieldType.equals("java.lang.Double") || fieldType.equals("double")) {
                    field.set(bean, Double.valueOf(value));
                } else if (fieldType.equals("java.math.BigDecimal")) {
                    field.set(bean, new BigDecimal(value));
                } else if (fieldType.equals("java.util.Date")) {
                    Date d = DateUtil.parse(value, DateUtil.FORMAT_6);
                    if (d == null) {
                        d = DateUtil.parse(value, DateUtil.FORMAT_7);
                    }
                    field.set(bean, d);
                } else if (fieldType.equals("java.sql.Date")) {
                    Date d = DateUtil.parse(value, DateUtil.FORMAT_6);
                    if (d == null) {
                        d = DateUtil.parse(value, DateUtil.FORMAT_7);
                    }
                    field.set(bean, DateUtil.convertUtilDateToSQLDate(d));
                } else if (fieldType.equals("java.lang.Boolean") || fieldType.equals("boolean")) {
                    field.set(bean, Boolean.valueOf(value));
                } else if (fieldType.equals("java.lang.Byte") || fieldType.equals("byte")) {
                    field.set(bean, Byte.valueOf(value));
                } else if (fieldType.equals("java.lang.Short") || fieldType.equals("short")) {
                    field.set(bean, Short.valueOf(value));
                }
            } catch (NumberFormatException ex) {
                // When using a simple data types will throw an exception
                logger.info(ex.getMessage(), ex);
            } catch (Exception e) {
                field.set(bean, null);
                logger.info(e.getMessage(), e);
            }
        }
    }

    /**
     * @description :save form and remove start string,simple to transfer String[]
     * 
     * @param request
     * @param prefix
     * @return
     */
    @SuppressWarnings("rawtypes")
    public Map<String, String[]> getParametersToMapArrayStartingWith(ServletRequest request, String prefix) {
        Assert.notNull(request, "Request must not be null");
        Enumeration paramNames = request.getParameterNames();
        Map<String, String[]> params = new TreeMap<String, String[]>();
        prefix = prefix == null ? "" : prefix;
        while (paramNames != null && paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            if ("".equals(prefix) || paramName.startsWith(prefix)) {
                String unprefixed = paramName.substring(prefix.length());
                String[] values = request.getParameterValues(paramName);
                params.put(unprefixed, values);
            }
        }
        return params;
    }

    /**
     * @description : get query parameter, start with Q_
     * 
     * @author songlin.li
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")
    public Map<String, String[]> getQueryParameters(ServletRequest request) {
        Assert.notNull(request, "Request must not be null");
        Enumeration<String> paramNames = request.getParameterNames();
        Map<String, String[]> params = new TreeMap<String, String[]>();

        while (paramNames != null && paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            if (paramName.startsWith("Q_")) {
                String unprefixed = paramName.substring(2);
                String[] values = request.getParameterValues(paramName);
                if (values != null && values.length > 0) {
                    params.put(unprefixed, values);
                    request.setAttribute(unprefixed, values);
                }
            }
        }
        return params;
    }

    /**
     * @description : Description of the mothod
     * 
     * @author songlin.li
     * @param request
     * @param prefix
     * @return
     */
    public Map<String, String> getParametersToSimpleMapStartingWith(ServletRequest request, String prefix) {
        Map<String, String[]> params = getParametersToMapArrayStartingWith(request, prefix);
        Map<String, String> aMap = new HashMap<String, String>();
        for (String key : params.keySet()) {
            String[] values = params.get(key);
            if (values.length < 1 || StringUtil.isBlank(values[0]))
                continue;
            StringBuffer value = new StringBuffer();
            for (String v : values) {
                value.append(v).append(",");
            }
            aMap.put(key, value.substring(0, value.length() - 1));
        }
        return aMap;
    }

    /**
     * @description : start entity is key, like user.userName
     * 
     * @author songlin.li
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")
    public Map<String, Map<String, Object[]>> parameterToEntityMap(ServletRequest request) {
        Map<String, String[]> m = request.getParameterMap();
        Map<String, String> e = Maps.newLinkedHashMap();
        for (String key : m.keySet()) {
            String entity = key.split("\\.")[0];
            e.put(entity, "");
        }
        Map<String, Map<String, Object[]>> mm = Maps.newLinkedHashMap();
        for (String entity : e.keySet()) {
            Map<String, Object[]> map = Maps.newLinkedHashMap();
            for (String key : m.keySet()) {
                String[] values = m.get(key);
                String entityKey = key.split("\\.")[1];
                // entityKey = key.substring(key.indexOf(".")+1);
                if (key.startsWith(entity)) {
                    map.put(entityKey, values);
                }
            }
            mm.put(entity, map);
        }
        return mm;
    }

    /***
     * @description : save form same name's values to on value Splice with ","
     * 
     * @author songlin.li
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")
    public Map<String, String> parametersFormat(ServletRequest request) {
        Map<String, String[]> map = request.getParameterMap();
        Map<String, String> aMap = new HashMap<String, String>();
        for (String key : map.keySet()) {
            String[] values = request.getParameterValues(key);
            if (values.length < 1 || StringUtil.isBlank(values[0]))
                continue;
            StringBuffer value = new StringBuffer();
            for (String v : values) {
                value.append(v).append(",");
            }
            aMap.put(key, value.substring(0, value.length() - 1));
        }
        return aMap;
    }

}
